/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package central.server;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import share.AliveCheckThread;
import share.SocketCommunicator;
import share.MessageType;
import share.Server;

/**
 *
 * @author mire
 */
public class CentralServerThread implements Runnable {

    private SocketCommunicator socComm;
    private Socket socket;
    private CentralServer centralServer;
    private MessageType msg;
    private final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy");

    public CentralServerThread(Socket clientSocket, CentralServer server) {
        this.socket = socket;
        this.centralServer = server;
    }

    @Override
    public void run() {
        String status = null;
        String threadInfo = dateFormat.format(new Date());
        threadInfo += " " + this.hashCode() + " ";
        System.out.println(threadInfo);


        try {
            socComm = new SocketCommunicator(socket);
            socComm.open();
            String request = socComm.readString();
            threadInfo += socComm.getAddress() + " " + request + " ";
            System.out.println(threadInfo);

            try {
                msg = MessageType.valueOf(request);
            } catch (IllegalArgumentException ex) {
                status = "ERROR_NO_REQUEST";
                System.out.println(threadInfo + status);
                socComm.writeString(status);
                socComm.close();
                return;
            }

            if (socComm.getPort() == centralServer.getOperatorPort()
                    && msg != MessageType.NEW_SERVER) {
                status = "ERROR_WRONG_REQUEST";
                System.out.println(threadInfo + status);
                socComm.writeString(status);
                socComm.close();
                return;
            }

            switch (msg) {
                case ALIVE: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    socComm.writeString("YES");
                    socComm.close();
                    break;
                }
                //</editor-fold>

                case NEW_SERVER://<editor-fold defaultstate="collapsed" desc="...">
                {
                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    boolean updated = false;

                    String operatorName = socComm.readString();
                    Server s = (Server) socComm.readObject();
                    threadInfo += operatorName + " ";

                    if (centralServer.getOperators().containsKey(operatorName)) {
                        status = "ADDING_NEW_SERVER";
                        System.out.println(threadInfo + status);
                        updated = addNewServer(operatorName, s);

                    } else {
                        status = "ADDING_NEW_OPERATOR";
                        System.out.println(threadInfo + status);
                        addNewOperator(operatorName, s);
                        updated = true;
                    }
                    //adding listen thread
                    centralServer.startListener(operatorName, s);

                    //responce
                    status = "SERVER_ADDED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    if (!updated) {
                        centralServer.getOperators().get(operatorName).update(centralServer);
                    }
                    break;
                }
                //</editor-fold>

                case NEW_USER: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    status = "REQUEST_ACCEPTED";
                    socComm.writeString(status);
                    System.out.println(threadInfo + status);

                    String operatorName = socComm.readString();
                    CentralOperator operator = (CentralOperator) centralServer.getOperators().get(operatorName);

                    threadInfo += operatorName + " ";
                    if (!operator.getServers().contains(new Server(socComm.getAddress()))) {
                        status = "ERROR_UNKNOWN_SERVER";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }
                    status = "OK";
                    socComm.writeString(status);

                    operator.updateServerStatus(socComm.getAddress());

                    int userID = socComm.readInt();
                    String userNumber = socComm.readString();

                    status = "ADDING_USER";
                    System.out.println(threadInfo + status);
                    //dodavanje korisnika
                    operator.getUsers().put(userID, userNumber);
                    //backup
                    operator.backupUsers();
                    //responce
                    status = "USER_ADDED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    break;
                }
                //</editor-fold>

                case NEW_MULTIPLE_USERS: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    String operatorName = socComm.readString();
                    CentralOperator operator = (CentralOperator) centralServer.getOperators().get(operatorName);

                    threadInfo += operatorName + " ";
                    if (!operator.getServers().contains(new Server(socComm.getAddress()))) {
                        status = "ERROR_UNKNOWN_SERVER";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }
                    status = "OK";
                    socComm.writeString(status);

                    operator.updateServerStatus(socComm.getAddress());

                    String usersString = socComm.readString();

                    status = "ADDING_MULTIPLE_USERS";
                    System.out.println(threadInfo + status);
                    //dodavanje korisnika
                    ConcurrentHashMap<Integer, String> usersList = operator.parseUsers(usersString);
                    operator.getUsers().putAll(usersList);
                    //backup
                    operator.backupUsers();
                    //responce
                    status = "MULTIPLE_USERS_ADDED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    break;
                }
                //</editor-fold>

                case TRANSFER: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    String newOperatorName = socComm.readString();
                    CentralOperator newOperator = centralServer.getOperators().get(newOperatorName);
                    String oldOperatorName = socComm.readString();
                    CentralOperator oldOperator = centralServer.getOperators().get(oldOperatorName);

                    threadInfo += newOperatorName + " ";
                    if (oldOperator == null || newOperator == null) {
                        status = "ERROR_NO_OPERATOR";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }

                    if (!newOperator.getServers().contains(new Server(socComm.getAddress()))) {
                        status = "ERROR_UNKNOWN_SERVER";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }

                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    newOperator.updateServerStatus(socComm.getAddress());
                    //chitaj korisnika
                    int userID = socComm.readInt();
                    String userNumber = socComm.readString();

                    if (!oldOperator.getUsers().contains(userNumber)) {
                        if (newOperator.getUsers().contains(userNumber)) {
                            status = "USER_TRANSFERED";
                        } else {
                            status = "ERROR_NO_NUMBER";
                        }
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }
                    //responce
                    status = "JOB_GOT";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    //za sada gotovo sa prvim operaterom
                    status = "REMOVING_USER";
                    System.out.println(threadInfo + status);
//TODO transfer
                    //obrishi od starog operatera
                    boolean removed = false;
                    SocketCommunicator sc = null;
                    Iterator<Server> it = oldOperator.getServers().values().iterator();
                    while (it.hasNext() && !removed) {
                        Server server = it.next();

                        try {
                            sc = new SocketCommunicator(new Socket(server.getAddress(), server.getCentralPort()));
                            sc.open();

                            sc.writeString("REMOVE_USER");
                            sc.readString();
                            sc.writeInt(userID);
                            sc.writeString(userNumber);
                            sc.writeString(newOperatorName);
                            status = sc.readString();
                            if (!status.equals("USER_REMOVED")) {
                                break;
                            }

                            removed = true;
                            server.setOnline(true);
                        } catch (UnknownHostException ex) {
                            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                        } catch (IOException ex) {
                            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                    if (!removed) {
                        System.out.println(threadInfo + status);
                        sc.close();
                        break;
                    }
                    status = "TRANSFERING_USER_NEW_SERVER";
                    System.out.println(threadInfo + status);
                    status = transferUserNewServer(newOperator, userID, userNumber, oldOperatorName);
                    System.out.println(threadInfo + status);
                    sc.writeString(status);
                    sc.close();

                    if (!status.equals("USER_TRANSFERED")) {
                        break;
                    }

                    //update
                    oldOperator.getUsers().remove(userID, userNumber);
                    newOperator.getUsers().put(userID, userNumber);
                    //backup
                    newOperator.backupUsers();
                    oldOperator.backupUsers();
                    break;
                }
                //</editor-fold>

                case MULTIPLE_TRANSFER: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    String newOperatorName = socComm.readString();
                    CentralOperator newOperator = centralServer.getOperators().get(newOperatorName);
                    String oldOperatorName = socComm.readString();
                    CentralOperator oldOperator = centralServer.getOperators().get(oldOperatorName);

                    threadInfo += newOperatorName + " ";
                    if (oldOperator == null || newOperator == null) {
                        status = "ERROR_NO_OPERATOR";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }

                    if (!newOperator.getServers().contains(new Server(socComm.getAddress()))) {
                        status = "ERROR_UNKNOWN_SERVER";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }
                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    newOperator.updateServerStatus(socComm.getAddress());
                    //chitaj korisnike
                    String usersString = socComm.readString();

                    ConcurrentHashMap<Integer, String> usersList = oldOperator.parseUsers(usersString);
                    if (!containsAll(oldOperator, usersList)) {
                        if (containsAll(newOperator, usersList)) {
                            status = "USER_TRANSFERED";
                        } else {
                            status = "ERROR_NO_NUMBER";
                        }
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }
                    //responce
                    status = "JOB_GOT";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    //za sada gotovo sa prvim operaterom
                    status = "REMOVING_MUTLRIPLE_USERS";
                    System.out.println(threadInfo + status);

                    //TODO MULTIPLE TRANSFER
                    //obrishi od starog operatera
                    boolean removed = false;
                    SocketCommunicator sc = null;
                    Iterator<Server> it = oldOperator.getServers().values().iterator();
                    while (it.hasNext() && !removed) {
                        Server server = it.next();

                        try {
                            sc = new SocketCommunicator(new Socket(server.getAddress(), server.getCentralPort()));
                            sc.open();

                            sc.writeString("REMOVE_MULTIPLE_USERS");
                            sc.readString();
                            sc.writeString(newOperatorName);
                            sc.writeString(usersString);
                            status = sc.readString();
                            if (!status.equals("MULTIPLE_USERS_REMOVED")) {
                                break;
                            }
                            removed = true;
                            server.setOnline(true);
                        } catch (UnknownHostException ex) {
                            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                        } catch (IOException ex) {
                            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                    if (!removed) {
                        System.out.println(threadInfo + status);
                        sc.close();
                        break;
                    }
                    status = "TRANSFERING_MULTIPLE_USERS_NEW_SERVER";
                    System.out.println(threadInfo + status);
                    status = transferMutlipleUsersNewServer(newOperator, usersString, oldOperatorName);
                    System.out.println(threadInfo + status);
                    sc.writeString(status);
                    sc.close();

                    if (!status.equals("MULTIPLE_USERS_REMOVED")) {
                        break;
                    }
                    //update
                    removeAll(oldOperator.getUsers(), usersList);
                    newOperator.getUsers().putAll(usersList);
                    //backup
                    newOperator.backupUsers();
                    oldOperator.backupUsers();
                    break;
                }
                //</editor-fold>

                case DELETE_USER: //<editor-fold defaultstate="collapsed" desc="...">
                {
                    status = "REQUEST_ACCEPTED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);

                    String operatorName = socComm.readString();
                    CentralOperator operator = (CentralOperator) centralServer.getOperators().get(operatorName);

                    threadInfo += operatorName + " ";
                    if (!operator.getServers().contains(new Server(socComm.getAddress()))) {
                        status = "ERROR_UNKNOWN_SERVER";
                        System.out.println(threadInfo + status);
                        socComm.writeString(status);
                        socComm.close();
                        break;
                    }
                    status = "OK";
                    socComm.writeString(status);

                    operator.updateServerStatus(socComm.getAddress());

                    int userID = socComm.readInt();
                    String userNumber = socComm.readString();

                    status = "DELETING_USER";
                    System.out.println(threadInfo + status);
                    operator.getUsers().remove(userID, userNumber);
                    //backup
                    operator.backupUsers();
                    //responce
                    status = "USER_DELETED";
                    System.out.println(threadInfo + status);
                    socComm.writeString(status);
                    socComm.close();
                    break;
                }
                //</editor-fold>

            }
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SocketException ex) {
            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
        } catch (OutOfMemoryError err) {
            Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, err);
            try {
                socComm.writeString("ERROR_OOM");
            } catch (IOException ex) {
            }
        } finally {
            try {
                socComm.close();
            } catch (IOException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            }
            status = "FINISHED";
            System.out.println(threadInfo + status);
        }
    }

    private void addNewOperator(String operatorName, Server s) {
        //add new operator
        CentralOperator operator = new CentralOperator(operatorName);
        centralServer.getOperators().put(operatorName, operator);
        //add server
        operator.getServers().put(s.hashCode(), s);
        operator.backupServers();
        //backup
        centralServer.backupConfiguration();
        //set CheckAliveThread
        operator.setAliveCheckThread(Executors.newSingleThreadScheduledExecutor());
        operator.getAliveCheckThread().scheduleAtFixedRate(
                new AliveCheckThread(operator.getServers(), 0, operator.getBackupServersName()),
                centralServer.getAliveTimeout(), centralServer.getAliveTimeout(), TimeUnit.SECONDS);
    }

    private boolean addNewServer(String operatorName, Server s) {
        // operator exists

        CentralOperator operator = centralServer.getOperators().get(operatorName);
        //add server
        ConcurrentHashMap<Integer, Server> servers = operator.getServers();
        if (servers.contains(s)) {
            Server replace = servers.replace(s.hashCode(), s);
            replace = null;
        } else {
            servers.put(s.hashCode(), s);
        }
        //backup
        if (!operator.isUpdated()) {
            operator.backupServers();
            return false;
        }
        return true;
    }

    private boolean removeUserFromOldServer(CentralOperator oldOperator, int userID, String userNumber, String newOperatorName) {
        boolean removed = false;
        SocketCommunicator sc = null;
        Iterator<Server> it = oldOperator.getServers().values().iterator();
        while (it.hasNext() && removed == false) {
            Server server = it.next();

            try {
                sc = new SocketCommunicator(new Socket(server.getAddress(), server.getCentralPort()));
                sc.open();

                sc.writeString("REMOVE_USER");
                sc.readString();
                sc.writeInt(userID);
                sc.writeString(userNumber);
                sc.writeString(newOperatorName);
                if (!sc.readString().equals("USER_REMOVED")) {
                    continue;
                }
                removed = true;
                server.setOnline(true);
            } catch (UnknownHostException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    sc.close();
                } catch (IOException ex) {
                    Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        return removed;
    }

    private boolean removeMultipleUsersFromOldServer(CentralOperator oldOperator, String usersString, String newOperatorName) {
        boolean removed = false;
        SocketCommunicator sc = null;

        Iterator<Server> it = oldOperator.getServers().values().iterator();
        while (it.hasNext() && removed == false) {
            Server server = it.next();

            try {
                sc = new SocketCommunicator(new Socket(server.getAddress(), server.getCentralPort()));
                sc.open();

                sc.writeString("REMOVE_MULTIPLE_USERS");
                sc.readString();
                sc.writeString(newOperatorName);
                sc.writeString(usersString);
                //sc.writeObject(new Date());
                if (!sc.readString().equals("MULTIPLE_USERS_REMOVED")) {
                    continue;
                }
                removed = true;
                server.setOnline(true);
            } catch (UnknownHostException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                if (sc != null) {
                    try {
                        sc.close();
                    } catch (IOException ex) {
                        Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        return removed;
    }

    private String transferUserNewServer(CentralOperator newOperator, int userID, String userNumber, String oldOperatorName) {
        boolean removed = false;
        SocketCommunicator sc = null;
        Iterator<Server> it = newOperator.getServers().values().iterator();
        String returnStatus = "ERROR_NOT_REMOVED";
        while (it.hasNext() && removed == false) {
            Server server = it.next();

            try {
                sc = new SocketCommunicator(new Socket(server.getAddress(), server.getCentralPort()));
                sc.open();

                sc.writeString("TRANSFER");
                sc.readString();
                sc.writeInt(userID);
                sc.writeString(userNumber);
                sc.writeString(oldOperatorName);
                returnStatus = sc.readString();

                if (!returnStatus.equals("USER_TRANSFERED")) {
                    continue;
                }
                removed = true;
                server.setOnline(true);
            } catch (UnknownHostException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    sc.close();
                } catch (IOException ex) {
                    Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        return returnStatus;
    }

    private String transferMutlipleUsersNewServer(CentralOperator newOperator, String usersString, String oldOperatorName) {
        boolean removed = false;
        SocketCommunicator sc = null;

        Iterator<Server> it = newOperator.getServers().values().iterator();
        String returnStatus = "ERROR_NOT_REMOVED";
        while (it.hasNext() && removed == false) {
            Server server = it.next();

            try {
                sc = new SocketCommunicator(new Socket(server.getAddress(), server.getCentralPort()));
                sc.open();

                sc.writeString("MULTIPLE_TRANSFER");
                sc.readString();
                sc.writeString(oldOperatorName);
                sc.writeString(usersString);
                returnStatus = sc.readString();
                if (!returnStatus.equals("MULTIPLE_USERS_TRANSFERED")) {
                    continue;
                }
                removed = true;
                server.setOnline(true);
            } catch (UnknownHostException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                if (sc != null) {
                    try {
                        sc.close();
                    } catch (IOException ex) {
                        Logger.getLogger(CentralServerThread.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        return returnStatus;
    }

    private boolean containsAll(CentralOperator operator, ConcurrentHashMap<Integer, String> usersList) {
        HashSet<Integer> operatorUsers = new HashSet<Integer>(operator.getUsers().keySet());
        return operatorUsers.containsAll(usersList.keySet());
    }

    private void removeAll(ConcurrentHashMap<Integer, String> usersList, ConcurrentHashMap<Integer, String> oldUsers) {
        for (Iterator<Integer> it = oldUsers.keySet().iterator(); it.hasNext();) {
            int key = it.next();
            usersList.remove(key, oldUsers.get(key));
        }
    }
}
